namespace Iso {
    export interface Point {
        x: number;
        y: number;
    }

    export type Point4 = [Point, ...Point[]] & { length: 4 };

    const INVALID_RELATION = Number.MAX_VALUE;

    export class Obj {
        readonly lp: Point; // 左端点
        readonly rp: Point; // 右端点
        public udata: any = null; // 用户数据指针

        /**
         * 坐标系是普通的二维直角坐标系。x+ 向右，y+ 向上
         * @param points 组成一个矩形的4个端点
         */
        constructor(points: Point4, udata = null) {
            // 按 x 排序 points
            points.sort((a, b) => a.x - b.x);
            // 左端点为x最小，y最小
            this.lp = ((points[0].x < points[1].x) || (points[0].y < points[1].y)) ? points[0] : points[1];
            // 右端点为x最大，y最大
            this.rp = ((points[3].x > points[2].x) || (points[3].y > points[2].y)) ? points[3] : points[2];

            this.udata = udata;
        }

        get minY(): number {
            return Math.min(this.lp.y, this.rp.y);
        }

        get maxY(): number {
            return Math.max(this.lp.y, this.rp.y);
        }

        get mp(): Point {
            return { x: (this.lp.x + this.rp.x) / 2, y: (this.lp.y + this.rp.y) / 2 };
        }

        /**
         * 比较两个对象的遮挡关系。该函数可以用于 Array.sort 升序排序
         * @returns 0 -> a 和 b 不存在遮挡关系
         * @returns 1 -> a 遮挡 b
         * @returns -1 -> b 遮挡 a
         */
        static compare(a: Obj, b: Obj): number {
            const rel = Obj.compareLineSection(a, b);
            return (rel !== INVALID_RELATION) ? rel : Obj.compareCenterY(a, b);
        }

        private static compareLineSection(a: Obj, b: Obj): number {
            // 如果 x 轴上的投影没有相交，则认为两个物体无关
            if ((a.rp.x < b.lp.x) || (b.rp.x < a.lp.x)) return 0;

            // 如果 y 轴上投影没有相交，则直接根据 y 来判定
            if (a.maxY < b.minY) return 1;
            if (b.maxY < a.minY) return -1;

            // 通过线段的叉乘关系来判断前后
            const dxa = a.rp.x - a.lp.x;
            const dya = a.lp.y - a.rp.y;
            const dxb = b.rp.x - b.lp.x;
            const dyb = b.lp.y - b.rp.y;
            const det = dya * dxb - dxa * dyb;
            const dx = b.lp.x - a.lp.x;
            const dy = a.lp.y - b.lp.y;

            if (Math.abs(det) < 0.01) {
                const d = dx * dya - dy * dxa;
                if (d < -0.001) return -1;
                if (d > 0.001) return 1;
                if (a.lp.x < b.lp.x) return -1;
                if (a.lp.x > b.lp.x) return 1;
                if (a.rp.x > b.rp.x) return -1;
                if (a.rp.x < b.rp.x) return 1;

                return INVALID_RELATION;
            }

            const e1 = (dxb * dy - dyb * dx) / det;
            const e2 = (dxa * dy - dya * dx) / det;
            let d = 0;
            if (e1 < 0) d = dx * dyb - dy * dxb;
            else if (e1 > 1) d = (b.lp.y - a.rp.y) * dxb + (b.lp.x - a.rp.x) * dyb;
            else if (e2 < 0) d = dx * dya - dy * dxa;
            else if (e2 > 1) d = (b.rp.y - a.lp.y) * dxa + (b.rp.x - a.lp.x) * dya;

            if (d < 0) return -1;
            if (d > 0) return 1;

            return INVALID_RELATION;
        }

        private static compareCenterY(a: Obj, b: Obj): number {
            const ca = a.mp;
            const cb = b.mp;
            if (ca.y > cb.y) return -1;
            if (ca.y < cb.y) return 1;
            if (ca.x < cb.x) return -1;
            if (ca.x > cb.x) return 1;
            return 0
        }
    }
}

// 测试前后关系
const obj1 = new Iso.Obj([
    { x: 0, y: 1 },
    { x: 1, y: 0 },
    { x: 3, y: 2 },
    { x: 2, y: 3 }
]);

const tests = [
    { t: -1, o: new Iso.Obj([{ x: 2, y: 1 }, { x: 3, y: 0 }, { x: 4, y: 1 }, { x: 3, y: 2 }]) },
    { t: 1, o: new Iso.Obj([{ x: 0, y: 1 }, { x: 1, y: 2 }, { x: 0, y: 3 }, { x: -1, y: 2 }]) },
    { t: 1, o: new Iso.Obj([{ x: 0, y: 2 }, { x: 1, y: 3 }, { x: 0, y: 4 }, { x: -1, y: 3 }]) }, // y轴投影不相交
    { t: 0, o: new Iso.Obj([{ x: -3, y: -1 }, { x: -2, y: 0 }, { x: -3, y: 1 }, { x: -4, y: 0 }]) }, // x 轴投影不相交
    { t: 0, o: new Iso.Obj([{ x: 7, y: -1 }, { x: 8, y: 0 }, { x: 8, y: 1 }, { x: 7, y: 0 }]) }, // x 轴投影不相交
    { t: 1, o: new Iso.Obj([{ x: 4, y: 1 }, { x: 5, y: 2 }, { x: 3, y: 4 }, { x: 2, y: 3 }]) },
    { t: 0, o: new Iso.Obj([{ x: 1.5, y: 1 }, { x: 2.0, y: 1.5 }, { x: 1.5, y: 2 }, { x: 1.0, y: 1.5 }]) }, // 比较 cy
    { t: -1, o: new Iso.Obj([{ x: 1.9, y: 1 }, { x: 2.4, y: 1.5 }, { x: 1.9, y: 2 }, { x: 1.4, y: 1.5 }]) }, // 比较 cy
    { t: 1, o: new Iso.Obj([{ x: 1.1, y: 1 }, { x: 1.6, y: 1.5 }, { x: 1.1, y: 2 }, { x: 0.6, y: 1.5 }]) }, // 比较 cy
    { t: -1, o: new Iso.Obj([{ x: 1.5, y: 0.9 }, { x: 2.0, y: 1.4 }, { x: 1.5, y: 1.9 }, { x: 1.0, y: 1.4 }]) }, // 比较 cy
    { t: 1, o: new Iso.Obj([{ x: 1.5, y: 1.1 }, { x: 2.0, y: 1.6 }, { x: 1.5, y: 2.1 }, { x: 1.0, y: 1.6 }]) }, // 比较 cy
    { t: -1, o: new Iso.Obj([{ x: 1, y: 0 }, { x: 2.0, y: 1 }, { x: 1, y: 2 }, { x: 0, y: 1 }]) }, // 比较 cy
    { t: 1, o: new Iso.Obj([{ x: 2, y: 1 }, { x: 3, y: 2 }, { x: 2, y: 3 }, { x: 1, y: 2 }]) }, // 比较 cy
];

for (const o of tests) {
    const r = Iso.Obj.compare(obj1, o.o);
    console.log(r, r == o.t ? 'pass' : 'fail');
}

// 测试矩形赋值对角的正确判定
const obj2 = new Iso.Obj([{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 }]);
console.log('测试对角判定', obj2.lp.x == 0 && obj2.lp.y == 0 && obj2.rp.x == 1 && obj2.rp.y == 1 ? 'pass' : 'fail');
